 <template>
  <el-form
    ref="formRef"
    :model="formData"
    :rules="formRules"
    :label-position="labelPosition"
    :label-width="labelWidth"
    :size="size"
    :inline="inline"
    :validate-on-rule-change="validateOnRuleChange"
    :hide-required-asterisk="hideRequiredAsterisk"
    :scroll-to-error="scrollToError"
    :status-icon="statusIcon"
    :disabled="disabled"
    class="m-form"
  >
    <template v-for="(item, index) in formItems" :key="index">
      <el-form-item
        v-if="!item.hidden"
        :label="item.label"
        :prop="item.prop"
        :label-width="item.labelWidth"
        :required="item.required"
        :rules="item.rules"
        :error="item.error"
        :show-message="item.showMessage !== false"
        :inline-message="item.inlineMessage"
        :size="item.size || size"
      >
        <!-- 根据不同的组件类型渲染不同的表单控件 -->
        <component
          v-if="item.component"
          :is="item.component"
          v-model="formData[item.prop]"
          v-bind="item.props || {}"
          @change="(val: any) => handleChange(val, item)"
        ></component>

        <!-- Input 输入框 -->
        <el-input
          v-else-if="item.type === 'input'"
          v-model="formData[item.prop]"
          v-bind="item.props || {}"
          :placeholder="item.placeholder || `请输入${item.label}`"
          :disabled="item.disabled || disabled"
          @change="(val: any) => handleChange(val, item)"
        />

        <!-- Input Number 数字输入框 -->
        <el-input-number
          v-else-if="item.type === 'number'"
          v-model="formData[item.prop]"
          v-bind="item.props || {}"
          :placeholder="item.placeholder || `请输入${item.label}`"
          :disabled="item.disabled || disabled"
          @change="(val: any) => handleChange(val, item)"
        />

        <!-- Select 选择器 -->
        <el-select
          v-else-if="item.type === 'select'"
          v-model="formData[item.prop]"
          v-bind="item.props || {}"
          :placeholder="item.placeholder || `请选择${item.label}`"
          :disabled="item.disabled || disabled"
          style="width: 100%"
          @change="(val: any) => handleChange(val, item)"
        >
          <el-option
            v-for="option in item.options"
            :key="option.value"
            :label="option.label"
            :value="option.value"
            :disabled="option.disabled"
          ></el-option>
        </el-select>

        <!-- Radio 单选框 -->
        <el-radio-group
          v-else-if="item.type === 'radio'"
          v-model="formData[item.prop]"
          v-bind="item.props || {}"
          :disabled="item.disabled || disabled"
          @change="(val: any) => handleChange(val, item)"
        >
          <el-radio
            v-for="option in item.options"
            :key="option.value"
            :label="option.value"
            :disabled="option.disabled"
          >
            {{ option.label }}
          </el-radio>
        </el-radio-group>

        <!-- Checkbox 多选框 -->
        <el-checkbox-group
          v-else-if="item.type === 'checkbox'"
          v-model="formData[item.prop]"
          v-bind="item.props || {}"
          :disabled="item.disabled || disabled"
          @change="(val: any) => handleChange(val, item)"
        >
          <el-checkbox
            v-for="option in item.options"
            :key="option.value"
            :label="option.value"
            :disabled="option.disabled"
          >
            {{ option.label }}
          </el-checkbox>
        </el-checkbox-group>

        <!-- Switch 开关 -->
        <el-switch
          v-else-if="item.type === 'switch'"
          v-model="formData[item.prop]"
          v-bind="item.props || {}"
          :disabled="item.disabled || disabled"
          @change="(val: any) => handleChange(val, item)"
        />

        <!-- Slider 滑块 -->
        <el-slider
          v-else-if="item.type === 'slider'"
          v-model="formData[item.prop]"
          v-bind="item.props || {}"
          :disabled="item.disabled || disabled"
          @change="(val: any) => handleChange(val, item)"
        />

        <!-- TimePicker 时间选择器 -->
        <el-time-picker
          v-else-if="item.type === 'time'"
          v-model="formData[item.prop]"
          v-bind="item.props || {}"
          :placeholder="item.placeholder || `请选择${item.label}`"
          :disabled="item.disabled || disabled"
          style="width: 100%"
           popper-class="starMange_Picker"
          @change="(val: any) => handleChange(val, item)"
        />
        <!-- DatePicker 日期选择器 -->
        <el-date-picker
          v-else-if="item.type === 'date'||item.type === 'datetime'"
          v-model="formData[item.prop]"
          v-bind="item.props || {}"
          :placeholder="item.placeholder || `请选择${item.label}`"
          :disabled="item.disabled || disabled"
          style="width: 100%"
          popper-class="starMange_Picker"
          @change="(val: any) => handleChange(val, item)"
        />

        <!-- Cascader 级联选择器 -->
        <el-cascader
          v-else-if="item.type === 'cascader'"
          v-model="formData[item.prop]"
          v-bind="{ ...item.props, options: item.options }"
          :placeholder="item.placeholder || `请选择${item.label}`"
          :disabled="item.disabled || disabled"
          style="width: 100%"
          @change="(val: any) => handleChange(val, item)"
        />

        <!-- 自定义插槽 -->
        <slot
          v-else-if="item.slot"
          :name="item.slot"
          :form-data="formData"
          :item="item"
        ></slot>

        <!-- 默认使用 el-input -->
        <el-input
          v-else-if="item.type === 'input'"
          v-model="formData[item.prop]"
          v-bind="item.props || {}"
          :placeholder="item.placeholder || `请输入${item.label}`"
          :disabled="item.disabled || disabled"
          @change="(val: any) => handleChange(val, item)"
        >
        </el-input>
      </el-form-item>
    </template>
    <slot name="append"></slot>
  </el-form>
 </template>
 
 <script lang="ts">
 import { defineComponent } from 'vue'
 export default defineComponent({
   name: 'MForm',
 })
 </script>

<script setup lang="ts">
import { ref, reactive, watch, computed, onMounted, nextTick, markRaw } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
// 表单项配置接口
interface FormItemOption {
  value: any
  label: string
  disabled?: boolean
  children?: FormItemOption[] // 添加children属性支持级联选择器
  [key: string]: any // 添加索引签名以支持级联选择器需要的其他属性
}
// 表单项接口
interface FormItem {
  label: string
  prop: string
  type?: string
  placeholder?: string
  required?: boolean
  rules?: any
  hidden?: boolean
  disabled?: boolean
  component?: any
  props?: Record<string, any>
  options?: FormItemOption[]
  labelWidth?: string
  slot?: string
  error?: string
  showMessage?: boolean
  inlineMessage?: boolean
  size?: 'large' | 'default' | 'small'
  onChange?: (value: any, formData: any) => void
  [key: string]: any
}
// 组件属性接口
interface MFormProps {
  // 表单数据对象
  model?: Record<string, any>
  // 表单项配置
  items?: FormItem[]
  // 表单校验规则
  rules?: FormRules
  // 标签位置
  labelPosition?: 'left' | 'right' | 'top'
  // 标签宽度
  labelWidth?: string | number
  // 表单尺寸
  size?: 'large' | 'default' | 'small'
  // 行内表单
  inline?: boolean
  // 是否在rules改变时立即校验
  validateOnRuleChange?: boolean
  // 是否隐藏必填字段的标签旁边的红色星号
  hideRequiredAsterisk?: boolean
  // 当校验失败时，是否滚动到错误表单项
  scrollToError?: boolean
  // 是否在输入框中显示校验结果反馈图标
  statusIcon?: boolean
  // 是否禁用表单
  disabled?: boolean
}
// 定义props
const props = withDefaults(defineProps<MFormProps>(), {
  model: () => ({}),
  items: () => [],
  rules: () => ({}),
  labelPosition: 'right',
  labelWidth: 'auto',
  size: 'default',
  inline: false,
  validateOnRuleChange: true,
  hideRequiredAsterisk: false,
  scrollToError: false,
  statusIcon: false,
  disabled: false,
})
// 表单实例引用
const formRef = ref<FormInstance>()
// 响应式表单数据
const formData = reactive<Record<string, any>>({
  ...props.model,
})
// 表单项
const formItems = computed(() => props.items)
// 表单规则
const formRules = computed(() => {
  const rules: FormRules = { ...props.rules }
  // 如果表单项有自己的规则且没有在全局规则中定义，则添加到全局规则中
  formItems.value.forEach((item) => {
    if (item.rules && !rules[item.prop]) {
      rules[item.prop] = item.rules
    }
    // 如果表单项标记为必填且没有规则，则添加必填规则
    if (item.required && !rules[item.prop]) {
      rules[item.prop] = [{ required: true, message: `${item.type === 'select' ? '选择' : '输入'}${item.label}`, trigger: item.type === 'select' ? 'change' : 'blur' }]
    }
  })
  return rules
})
// 监听model变化，更新formData
watch(
  () => props.model,
  (newValue) => {
    Object.keys(newValue).forEach((key) => {
      formData[key] = newValue[key]
    })
  },
  { deep: true }
)
// 处理表单项的值变化
const handleChange = (value: any, item: FormItem) => {
  // 执行表单项自定义的onChange函数
  if (item.onChange) {
    item.onChange(value, formData)
  }
  // 触发自定义change事件
  emit('change', item.prop, value, formData)
}
// 定义事件
const emit = defineEmits<{
  (e: 'validate', prop: string, valid: boolean, message: string): void
  (e: 'change', prop: string, value: any, formData: Record<string, any>): void
  (e: 'submit', formData: Record<string, any>): void
}>()
// 表单验证方法
const validate = async () => {
  if (!formRef.value) return false
  return new Promise((resolve, reject) => {
    formRef.value!.validate((valid, fields) => {
      if (valid) {
        resolve("success")
      } else {
        reject(fields)
      }
    })
  })
}
// 验证特定字段
const validateField = async (prop: string | string[]) => {
  if (!formRef.value) return
  return formRef.value.validateField(prop)
}
// 重置表单
const resetFields = () => {
  if (!formRef.value) return
  formRef.value.resetFields()
}
// 清除验证状态
const clearValidate = (props?: string | string[]) => {
  if (!formRef.value) return 
  formRef.value.clearValidate(props)
}
// 滚动到指定字段
const scrollToField = (prop: string) => {
  if (!formRef.value) return
  formRef.value.scrollToField(prop)
}
// 暴露方法给父组件
defineExpose({
  formRef,
  formData,
  validate,
  validateField,
  resetFields,
  clearValidate,
  scrollToField,
})
// 初始化
onMounted(() => {
  nextTick(() => {
    formItems.value.forEach((item) => {
      if (formData[item.prop] === undefined) {
        formData[item.prop] = item.default !== undefined ? item.default : null
      }
    })
  })
})
 </script>
 
<style lang="scss" scoped>
.m-form {
  width: 100%;
  :deep(.el-form--inline) {
    display: flex;
    flex-wrap: wrap;
    .el-form-item {
      margin-right: 15px;
      margin-bottom: 15px;   
      // 设置合适的最小宽度，防止挤压
      &__content {
        display: flex;
        min-width: 160px;
      }
      .el-input,
      .el-select,
      .el-date-picker,
      .el-cascader {
        width: 100%;
        min-width: 160px;
      }
    }
  }
  :deep(.el-form-item) {
    margin-bottom: 18px;
    &.is-error {
      .el-input__wrapper {
        box-shadow: 0 0 0 1px var(--el-color-danger) inset;
      }
    }
    .el-form-item__label {
      padding-right: 12px;
      font-weight: normal;
      color: #fff;
    }
  }
}
</style>
 